package com.zmn.plat.business.impl.channel;

import com.zmn.common.utils.collection.CollectionUtil;
import com.zmn.common.utils.number.NumberUtil;
import com.zmn.common.utils.string.StringUtil;
import com.zmn.manager.redis.RedisManager;
import com.zmn.manager.redis.lock.DistributedLock;
import com.zmn.manager.redis.lock.DistributedLockUtils;
import com.zmn.plat.business.interfaces.channel.ChannelServiceCategoryBService;
import com.zmn.plat.common.constant.ProductConsts;
import com.zmn.plat.common.constant.RedisKeyConstant;
import com.zmn.plat.common.dictionary.PlatErrorCodeDict;
import com.zmn.plat.common.dto.category.CategoryUnionInfoDRO;
import com.zmn.plat.common.exception.PlatException;
import com.zmn.plat.model.entity.channel.ChannelServiceArea;
import com.zmn.plat.model.vo.channel.ChannelServiceAreaProductCategoryVo;
import com.zmn.plat.model.vo.channel.category.ChannelCategoryUnionVo;
import com.zmn.plat.services.interfaces.channel.ChannelServiceAreaCategoryService;
import com.zmn.plat.services.interfaces.channel.ChannelServiceAreaService;
import com.zmn.plat.services.interfaces.channel.ChannelServiceCategoryService;
import com.zmn.plat.utils.callback.CallbackUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author tanbiao
 * @description
 * @modifier
 * @since 2020/11/11 18:29
 **/
@Service
@Slf4j
public class ChannelServiceCategoryBServiceImpl implements ChannelServiceCategoryBService {

    @Resource
    private ChannelServiceCategoryService channelServiceCategoryService;

    @Resource
    private ChannelServiceAreaService channelServiceAreaService;

    @Resource
    private ChannelServiceAreaCategoryService channelServiceAreaCategoryService;

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public Integer getAvailableCategoryChannelId(Integer channelId) throws PlatException {

        String key = RedisKeyConstant.getKeyAvailableCategoryByChannelId(channelId);
        String value = redisTemplate.opsForValue().get(key);

        // 缓存中有数据
        if (StringUtil.isNotBlank(value)) {
            if (RedisKeyConstant.NO_DATA.equals(value)) {
                throw new PlatException(PlatErrorCodeDict.ERROR_CODE_CHANNEL_NO_CATEGORY);
            } else {
                return Integer.parseInt(value);
            }
        }
        int time = 60;
        String lockKey = RedisKeyConstant.getLockKeyAvailableCategoryByChannelId(channelId);
        DistributedLock redisLock = DistributedLockUtils.buildReentrantLock(lockKey, time);
        if (redisLock.tryLock()) {
            try {
                Predicate<Integer> predicate = t -> t < 1;
                // 重构时使用countByQuery
                Integer result = CallbackUtils.execute((e) -> getAvailableCategoryNumber(e), predicate, channelId);
                if (NumberUtil.isNullOrZero(result)) {
                    value = RedisKeyConstant.NO_DATA;
                } else {
                    value = result.toString();
                }
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES);
                if (NumberUtil.isNullOrZero(result)) {
                    throw new PlatException(PlatErrorCodeDict.ERROR_CODE_CHANNEL_NO_CATEGORY);
                }
                return result;
            } finally {
                try {
                    redisLock.unlock();
                } catch (Exception ignored) {}
            }
        } else {
            if (redisLock.lock(time, TimeUnit.SECONDS)) {
                try {
                    redisLock.unlock();
                } catch (Exception ignored) {}
                return getAvailableCategoryChannelId(channelId);
            } else {
                log.error("根据渠道id查询开通了分类的渠道id，获取redis锁超时");
                throw new PlatException(PlatErrorCodeDict.ERROR_CODE_TIME_OUT);
            }
        }

    }

    @Override
//    @Cacheable(key = "'dubbo:channel:union:category:channelid:'+#p0+':cityid:'+#p1+':servcategids:'+#p2",cacheNames = "redis1h", unless = "#result == null || #result <= 0")
    public List<CategoryUnionInfoDRO> listUnionCategoryByChannelIdAndCityIdAndServCategIds(Integer channelId, Integer cityId, List<Integer> servCategIds) {
        if (NumberUtil.isNullOrZero(channelId) || NumberUtil.isNullOrZero(cityId) || CollectionUtil.isNullOrEmpty(servCategIds)) {
            log.error("渠道ID，城市ID，服务分类ID集合均不能为空，参数channelId：{}，cityId：{}，servCategIds：{}", channelId, cityId, servCategIds);
            throw new PlatException(PlatErrorCodeDict.ERROR_CODE_PANAM_NULL);
        }

        // 根据渠道查询开通城市列表
        List<ChannelServiceArea> channelServiceAreas = channelServiceAreaService.listCityByChannelId(channelId);

        List<ChannelCategoryUnionVo> result = Collections.EMPTY_LIST;

        // 如果渠道一个城市都没开通，则默认代表开通所有城市；如果渠道有开通城市数据，城市ID不再其中，则返回空集合，如果城市ID在其中，则查询对应的开通品类
        if (CollectionUtil.isNullOrEmpty(channelServiceAreas)) {
            List<ChannelCategoryUnionVo> channelCategoryUnionVos = channelServiceCategoryService.listCategoryUnionVoByChannelIdAndServCategIdList(channelId, servCategIds);
            result = channelCategoryUnionVos;
        } else {
            List<ChannelServiceArea> existDataList = channelServiceAreas.stream().filter(e -> Objects.equals(cityId, e.getCityId())).collect(Collectors.toList());
            if (CollectionUtil.isNotNullOrEmpty(existDataList)) {
                List<ChannelServiceAreaProductCategoryVo> innerData = channelServiceAreaCategoryService.listByChannelIdAndCityIdAndShowType(channelId, cityId, ProductConsts.EC_PRODUCT_TYPE);
                if (CollectionUtil.isNotNullOrEmpty(innerData)) {
                    result = channelServiceAreaCategoryService.listByChannelIdAndCityIdAndServCategIdList(channelId, cityId, servCategIds);
                } else {
                    result = channelServiceCategoryService.listCategoryUnionVoByChannelIdAndServCategIdList(channelId, servCategIds);
                }
            }
        }

        List<CategoryUnionInfoDRO> dros = result.stream().map(channelCategoryUnionVo -> {
            CategoryUnionInfoDRO dro  = new CategoryUnionInfoDRO();
            BeanUtils.copyProperties(channelCategoryUnionVo, dro);
            return dro;
        }).collect(Collectors.toList());

        return dros;
    }

    private Integer getAvailableCategoryNumber(Integer channelId) {
        Integer result = channelServiceCategoryService.countAvailableCategoryByChannelId(channelId);
        return result > 0 ? channelId : 0;
    }
}
